package com.aloda.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.aloda.ai.AiPlatformTypeExecutor;
import com.aloda.annotation.RateLimit;
import com.aloda.cache.RedisUtils;
import com.aloda.constant.*;
import com.aloda.context.UserBaseContext;
import com.aloda.dto.AiMessageDTO;
import com.aloda.dto.FlowableData;
import com.aloda.dto.question.QuestionAddDTO;
import com.aloda.dto.question.QuestionContentDTO;
import com.aloda.dto.question.QuestionEditDTO;
import com.aloda.dto.question.QuestionGenerateDTO;
import com.aloda.dto.user.UserSafetyDTO;
import com.aloda.entity.App;
import com.aloda.entity.Question;
import com.aloda.entity.UserToken;
import com.aloda.enums.*;
import com.aloda.exception.BaseException;
import com.aloda.exception.WarnException;
import com.aloda.mapper.AppMapper;
import com.aloda.mapper.QuestionMapper;
import com.aloda.mapper.UserTokenMapper;
import com.aloda.query.QuestionPageDTO;
import com.aloda.result.PageResult;
import com.aloda.service.QuestionService;
import com.aloda.utils.TokensTreatUtils;
import com.aloda.vo.question.QuestionContentVO;
import com.aloda.vo.question.QuestionListVO;
import com.aloda.vo.question.QuestionPageVO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.reactivex.Flowable;
import io.reactivex.schedulers.Schedulers;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @author aloda
 * @description 针对表【question(题目)】的数据库操作Service实现
 * @createDate 2025-03-05 16:27:38
 */
@Slf4j
@Service
public class QuestionServiceImpl extends ServiceImpl<QuestionMapper, Question> implements QuestionService {

    @Resource
    private QuestionMapper questionMapper;

    @Resource
    private AppMapper appMapper;

    @Resource
    private AiPlatformTypeExecutor aiPlatformTypeExecutor;

    @Resource
    private UserTokenMapper userTokenMapper;

    @Resource
    private RedisUtils redisUtils;

    @Resource
    private RedissonClient redissonClient;

    /**
     * 管理端新增题目
     *
     * @param questionAddDTO
     * @return
     */
    @Override
    public long addQuestion(QuestionAddDTO questionAddDTO) {
        if (questionAddDTO.getAppId() == null) {
            throw new WarnException(ErrorCode.PARAMS_ERROR);
        }
        // 1.后期可能需要对题目内容进行审核
        checkQuestionContent(questionAddDTO.getQuestionContent());
        // 2.校验appId是否正确
        App app = appMapper.selectById(questionAddDTO.getAppId());
        if (BeanUtil.isEmpty(app)) {
            throw new BaseException(ExceptionConstant.APP_NOT_EXIST);
        }
        // 校验该应用是否已经有题目
        LambdaQueryWrapper<Question> questionLambdaQueryWrapper = new LambdaQueryWrapper<>();
        questionLambdaQueryWrapper.eq(Question::getAppId, questionAddDTO.getAppId());
        Long aLong = questionMapper.selectCount(questionLambdaQueryWrapper);
        if (aLong > 0) {
            throw new BaseException(ExceptionConstant.APP_HAS_QUESTION);
        }
        // 3.新增
        String jsonString = JSON.toJSONString(questionAddDTO.getQuestionContent());
        Question question = new Question();
        question.setAppId(questionAddDTO.getAppId());
        question.setQuestionContent(jsonString);
        question.setUserId(app.getUserId());
        question.setCreateUser(app.getCreateUser());
        question.setCreateTime(LocalDateTime.now());
        question.setUpdateTime(LocalDateTime.now());
        questionMapper.insert(question);
        // 删除app对应的所有题目选项缓存
        String prefixKey = RedisConstant.APP_USER_ANSWER + app.getId().toString() + ":";
        redisUtils.deleteObjectFormCacheByPrefix(prefixKey);
        return question.getId();
    }

    /**
     * 分页查询
     *
     * @param questionPageDTO
     * @return
     */
    @Override
    public PageResult<QuestionPageVO> pageQuery(QuestionPageDTO questionPageDTO) {
        Page<Question> page = questionPageDTO.toMpPageDefaultSortByUpdateTimeDesc();
        LambdaQueryWrapper<Question> questionLambdaQueryWrapper = new LambdaQueryWrapper<>();
        questionLambdaQueryWrapper
                .like(questionPageDTO.getQuestionContent() != null, Question::getQuestionContent, questionPageDTO.getQuestionContent())
                .like(questionPageDTO.getAppId() != null, Question::getAppId, questionPageDTO.getAppId())
                .between(questionPageDTO.getBeginTime() != null && questionPageDTO.getEndTime() != null,
                        Question::getCreateTime, questionPageDTO.getBeginTime(), questionPageDTO.getEndTime());
        questionMapper.selectPage(page, questionLambdaQueryWrapper);
        return PageResult.of(page, QuestionPageVO.class);
    }

    /**
     * 编辑题目
     *
     * @param questionEditDTO
     */
    @Override
    public void editQuestion(QuestionEditDTO questionEditDTO) {
        App app = appMapper.selectById(questionEditDTO.getAppId());
        if (BeanUtil.isEmpty(app)) {
            throw new BaseException(ExceptionConstant.APP_NOT_EXIST);
        }
        String jsonString = JSON.toJSONString(questionEditDTO.getQuestionContent());
        LambdaQueryWrapper<Question> questionLambdaQueryWrapper = new LambdaQueryWrapper<>();
        questionLambdaQueryWrapper.eq(Question::getAppId, questionEditDTO.getAppId());
        Question question = new Question();
        question.setQuestionContent(jsonString);
        question.setAppId(questionEditDTO.getAppId());
        question.setUpdateTime(LocalDateTime.now());
        questionMapper.update(question, questionLambdaQueryWrapper);
        // 删除app对应的所有题目选项缓存
        String prefixKey = RedisConstant.APP_USER_ANSWER + app.getId().toString() + ":";
        redisUtils.deleteObjectFormCacheByPrefix(prefixKey);
    }

    /**
     * 根据appId查询题目内容
     *
     * @param id
     * @return
     */
    @Override
    public String getQuestionByAppId(Long id) {
        // 1.判断是否为创建用户
        App app = appMapper.selectById(id);
        if (BeanUtil.isEmpty(app)) {
            throw new BaseException(ExceptionConstant.APP_NOT_EXIST);
        }
        if (!app.getUserId().equals(UserBaseContext.getUser().getId())) {
            throw new BaseException(ErrorCode.FORBIDDEN_ERROR);
        }
        // 2.查询App对应的题目
        LambdaQueryWrapper<Question> questionLambdaQueryWrapper = new LambdaQueryWrapper<>();
        questionLambdaQueryWrapper.eq(Question::getAppId, id);
        Question question = questionMapper.selectOne(questionLambdaQueryWrapper);
        if (BeanUtil.isEmpty(question)) {
            return null;
        }
        return question.getQuestionContent();
    }

    /**
     * 新增/更新题目
     *
     * @param questionAddDTO
     * @return
     */
    @Override
    @Transactional
    @RateLimit(limit = 2)
    public Long addQuestionByUser(QuestionAddDTO questionAddDTO) {
        // 1.审核题目内容
        checkQuestionContent(questionAddDTO.getQuestionContent());
        // 2.判断应用是否存在且为创建者
        Long appId = questionAddDTO.getAppId();
        if (appId == null) {
            throw new WarnException(ErrorCode.PARAMS_ERROR);
        }
        App app = appMapper.selectById(appId);
        if (BeanUtil.isEmpty(app)) {
            throw new BaseException(ExceptionConstant.APP_NOT_EXIST);
        }
        UserSafetyDTO user = UserBaseContext.getUser();
        if (!app.getUserId().equals(user.getId())) {
            throw new BaseException(ErrorCode.FORBIDDEN_ERROR);
        }
        // 3.新增题目，如果app对应题目已存在则更新
        LambdaQueryWrapper<Question> questionLambdaQueryWrapper = new LambdaQueryWrapper<>();
        questionLambdaQueryWrapper.eq(Question::getAppId, appId);
        Question question = questionMapper.selectOne(questionLambdaQueryWrapper);
        if (BeanUtil.isEmpty(question)) {
            Question newQuestion = new Question();
            newQuestion.setQuestionContent(JSON.toJSONString(questionAddDTO.getQuestionContent()));
            newQuestion.setUserId(user.getId());
            newQuestion.setCreateUser(user.getUserAccount());
            newQuestion.setAppId(questionAddDTO.getAppId());
            newQuestion.setCreateTime(LocalDateTime.now());
            newQuestion.setUpdateTime(LocalDateTime.now());
            questionMapper.insert(newQuestion);
            // 更新app状态
            app.setReviewStatus(ReviewStatusEnum.REVIEWING.getValue());
            app.setUpdateTime(LocalDateTime.now());
            appMapper.updateById(app);
            // 删除app对应的所有题目选项缓存
            String prefixKey = RedisConstant.APP_USER_ANSWER + app.getId().toString() + ":";
            redisUtils.deleteObjectFormCacheByPrefix(prefixKey);
            return newQuestion.getId();
        }
        question.setQuestionContent(JSON.toJSONString(questionAddDTO.getQuestionContent()));
        question.setUpdateTime(LocalDateTime.now());
        questionMapper.updateById(question);
        // 更新app状态
        app.setReviewStatus(ReviewStatusEnum.REVIEWING.getValue());
        app.setUpdateTime(LocalDateTime.now());
        appMapper.updateById(app);
        // 删除app对应的所有题目选项缓存
        String prefixKey = RedisConstant.APP_USER_ANSWER + app.getId().toString() + ":";
        redisUtils.deleteObjectFormCacheByPrefix(prefixKey);
        return question.getId();
    }

    /**
     * 根据appId查询题目脱敏内容（开始答题的显示内容）
     *
     * @param id
     * @return
     */
    @Override
    public QuestionListVO getQuestionSafetyInfoList(Long id) {
        if (id == null) {
            throw new WarnException(ErrorCode.PARAMS_ERROR);
        }
        App app = appMapper.selectById(id);
        if (BeanUtil.isEmpty(app)) {
            throw new BaseException(ExceptionConstant.APP_NOT_EXIST);
        }
        if (!app.getReviewStatus().equals(ReviewStatusEnum.PASS.getValue())) {
            throw new BaseException(ExceptionConstant.APP_NOT_REVIEW);
        }
        LambdaQueryWrapper<Question> questionLambdaQueryWrapper = new LambdaQueryWrapper<>();
        questionLambdaQueryWrapper.eq(Question::getAppId, id);
        Question question = questionMapper.selectOne(questionLambdaQueryWrapper);
        QuestionListVO questionListVO = new QuestionListVO();
        questionListVO.setAppName(app.getAppName());
        if (BeanUtil.isEmpty(question)) {
            questionListVO.setList(Collections.emptyList());
            return questionListVO;
        }
        List<QuestionContentVO> list = JSONUtil.toList(question.getQuestionContent(), QuestionContentVO.class);
        list.forEach(questionContentVO -> questionContentVO.getOptions().forEach(option -> {
            option.setScore(0);
            option.setResult(null);
        }));
        questionListVO.setId(question.getId());
        questionListVO.setAppId(question.getAppId());
        questionListVO.setScoringStrategy(app.getScoringStrategy());
        questionListVO.setUserId(question.getUserId());
        questionListVO.setTotal(list.size());
        questionListVO.setList(list);
        return questionListVO;
    }

    /**
     * 客户端AI生成题目
     *
     * @param questionGenerateDTO
     * @return
     */
    @Override
    public SseEmitter aiGenerateQuestion(QuestionGenerateDTO questionGenerateDTO) {
        // 1.校验参数
        checkQuestionGenerateDTO(questionGenerateDTO);
        // 2.查询APP
        Long appId = questionGenerateDTO.getAppId();
        if (appId == null) {
            throw new WarnException(ErrorCode.PARAMS_ERROR);
        }
        App app = appMapper.selectById(appId);
        if (BeanUtil.isEmpty(app)) {
            throw new BaseException(ExceptionConstant.APP_NOT_EXIST);
        }
        Long userId = UserBaseContext.getUser().getId();
        String questionKey = "user:" + userId.toString() + ":ques-gen-lock";
        RLock lock = redissonClient.getLock(questionKey);
        // 获取当前线程id 异步任务通过线程id释放锁
        long thisId = Thread.currentThread().getId();
        try {
            if (lock.tryLock(10, 60, TimeUnit.SECONDS)) {
                LambdaQueryWrapper<UserToken> userTokenLambdaQueryWrapper = new LambdaQueryWrapper<>();
                userTokenLambdaQueryWrapper.eq(UserToken::getUserId, userId);
                UserToken userToken = userTokenMapper.selectOne(userTokenLambdaQueryWrapper);
                if (BeanUtil.isEmpty(userToken) || userToken.getTokenNum() <= 0) {
                    throw new BaseException(ExceptionConstant.USER_TOKEN_NOT_ENOUGH);
                }
                // 3.计算用户tokens是否足够本次调用
                Long needTokens = TokensTreatUtils.calculateTokensGenerateQuestion(questionGenerateDTO.getQuestionNum(),
                        questionGenerateDTO.getOptionNum(),
                        app.getAppName(),
                        questionGenerateDTO.getQuestionDesc());
                log.info("用户{}调用AI预计tokens:{}", userId, needTokens);
                if (needTokens > userToken.getTokenNum()) {
                    throw new BaseException(ExceptionConstant.USER_TOKEN_NOT_ENOUGH);
                }
                // 4.可以调用,进行AI生成题
                // 获取题目的用户消息
                String userMessage = getGenerateQuestionUserMessage(app,
                        questionGenerateDTO.getQuestionNum(),
                        questionGenerateDTO.getOptionNum(),
                        questionGenerateDTO.getQuestionDesc());
                // 建立 SSE 连接对象，0 表示不超时
                SseEmitter emitter = new SseEmitter(0L);

                // 获取AI消息数据传输对象
                AiMessageDTO aiMessageDTO = new AiMessageDTO();
                aiMessageDTO.setSystemMessage(AiConstant.GENERATE_QUESTION_SYSTEM_MESSAGE);
                aiMessageDTO.setUserMessage(userMessage);
                aiMessageDTO.setTemperature(AiConstant.DEFAULT_TEMPERATURE);
                AiPlatformEnum enumByValue = AiPlatformEnum.getEnumByValue(questionGenerateDTO.getPlatform());
                if (enumByValue == null) {
                    throw new BaseException(ExceptionConstant.PLATFORM_NOT_EXIST);
                }
                aiMessageDTO.setAiPlatform(enumByValue);
                AiModelEnums aiModel = AiModelEnums.getEnumByValue(questionGenerateDTO.getModel());
                if (aiModel == null) {
                    throw new BaseException(ExceptionConstant.MODEL_NOT_EXIST);
                }
                aiMessageDTO.setModel(aiModel.getValue());

                // AI 生成，sse 流式返回
                Flowable<FlowableData> modelDataFlowable = aiPlatformTypeExecutor.doStreamRequest(aiMessageDTO);
                StringBuilder contentBuilder = new StringBuilder();
                AtomicInteger flag = new AtomicInteger(0);
                AtomicLong totalTokens = new AtomicLong(0); // 用于记录总tokens
                modelDataFlowable
                        // 异步线程池执行
                        .observeOn(Schedulers.io())
                        .map(chunk -> {
                            Long tokens = chunk.getTotalTokens();
                            if (tokens != null) {
                                totalTokens.set(tokens);
                            }
                            return chunk;
                        })
                        .map(FlowableData::getMessage)
                        .map(message -> message.replaceAll("\\s", ""))
                        .filter(StrUtil::isNotBlank)
                        .flatMap(message -> {
                            // 将字符串转换为 List<Character>
                            List<Character> charList = new ArrayList<>();
                            for (char c : message.toCharArray()) {
                                charList.add(c);
                            }
                            return Flowable.fromIterable(charList);
                        })
                        .doOnNext(c -> {
                            {
                                // 识别第一个 [ 表示开始 AI 传输 json 数据，打开 flag 开始拼接 json 数组
                                if (c == '{') {
                                    flag.addAndGet(1);
                                }
                                if (flag.get() > 0) {
                                    contentBuilder.append(c);
                                }
                                if (c == '}') {
                                    flag.addAndGet(-1);
                                    if (flag.get() == 0) {
                                        // 累积单套题目满足 json 格式后，sse 推送至前端
                                        // sse 需要压缩成当行 json，sse 无法识别换行
                                        emitter.send(JSONUtil.toJsonStr(contentBuilder.toString()));
                                        // 清空 StringBuilder
                                        contentBuilder.setLength(0);
                                    }
                                }
                            }
                        })
                        .doOnError(error -> {
                            log.error("用户{}调用AI生成失败", userId, error);
                            // 异常时释放锁
                            if (lock.isHeldByThread(thisId)) {
                                lock.unlockAsync(thisId);
                            }
                            throw new BaseException(ExceptionConstant.AI_GENERATE_ERROR);
                        })
                        .doOnComplete(() -> {
                            // 扣除用户tokens
                            log.info("用户：{}调用AI生成题目，耗费：{}tokens", userId, totalTokens);
                            long usedTokens = totalTokens.get();
                            userToken.setTokenNum(userToken.getTokenNum() - usedTokens);
                            if (userToken.getTokenNum() < 0) {
                                userToken.setTokenNum(0L);
                            }
                            userTokenMapper.updateById(userToken);
                            try {
                                // 发送事件结束信息给前端
                                emitter.send(SseEmitter.event().data("EventFinnish"));
                            } catch (IOException e) {
                                log.error("发送事件结束信息失败", e);
                                throw new BaseException(ErrorCode.SYSTEM_ERROR);
                            } finally {
                                // 完成时释放锁
                                if (lock.isHeldByThread(thisId)) {
                                    lock.unlockAsync(thisId);
                                }
                                emitter.complete();
                            }
                        })
                        .subscribe();
                return emitter;
            } else {
                throw new BaseException(ExceptionConstant.SYSTEM_BUSY);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new BaseException(ErrorCode.SYSTEM_ERROR);
        }
    }

    /**
     * 保存AI生成的题目
     *
     * @param questionAddDTO
     */
    @Override
    public long addGenerateQuestion(QuestionAddDTO questionAddDTO) {
        // 1.审核题目内容
        checkQuestionContent(questionAddDTO.getQuestionContent());
        // 2.判断应用是否存在且为创建者
        Long appId = questionAddDTO.getAppId();
        if (appId == null) {
            throw new WarnException(ErrorCode.PARAMS_ERROR);
        }
        App app = appMapper.selectById(appId);
        if (BeanUtil.isEmpty(app)) {
            throw new BaseException(ExceptionConstant.APP_NOT_EXIST);
        }
        UserSafetyDTO user = UserBaseContext.getUser();
        if (!app.getUserId().equals(user.getId())) {
            throw new BaseException(ErrorCode.FORBIDDEN_ERROR);
        }
        // 3.新增题目，如果app对应题目已存在则追加
        LambdaQueryWrapper<Question> questionLambdaQueryWrapper = new LambdaQueryWrapper<>();
        questionLambdaQueryWrapper.eq(Question::getAppId, appId);
        Question question = questionMapper.selectOne(questionLambdaQueryWrapper);
        if (BeanUtil.isEmpty(question)) {
            Question newQuestion = new Question();
            newQuestion.setQuestionContent(JSON.toJSONString(questionAddDTO.getQuestionContent()));
            newQuestion.setUserId(user.getId());
            newQuestion.setCreateUser(user.getUserAccount());
            newQuestion.setAppId(questionAddDTO.getAppId());
            newQuestion.setCreateTime(LocalDateTime.now());
            newQuestion.setUpdateTime(LocalDateTime.now());
            questionMapper.insert(newQuestion);
            // 更新app状态
            app.setReviewStatus(ReviewStatusEnum.REVIEWING.getValue());
            app.setUpdateTime(LocalDateTime.now());
            appMapper.updateById(app);
            // 删除app对应的所有题目选项缓存
            String prefixKey = RedisConstant.APP_USER_ANSWER + app.getId().toString() + ":";
            redisUtils.deleteObjectFormCacheByPrefix(prefixKey);
            return newQuestion.getId();
        }
        // 向原有题目列表追加题目
        List<QuestionContentDTO> questionContentDTOS = JSONUtil.toList(question.getQuestionContent(), QuestionContentDTO.class);
        questionContentDTOS.addAll(questionAddDTO.getQuestionContent());
        question.setQuestionContent(JSON.toJSONString(questionContentDTOS));
        question.setUpdateTime(LocalDateTime.now());
        questionMapper.updateById(question);
        // 更新app状态
        app.setReviewStatus(ReviewStatusEnum.REVIEWING.getValue());
        app.setUpdateTime(LocalDateTime.now());
        appMapper.updateById(app);
        // 删除app对应的所有题目选项缓存
        String prefixKey = RedisConstant.APP_USER_ANSWER + app.getId().toString() + ":";
        redisUtils.deleteObjectFormCacheByPrefix(prefixKey);
        return question.getId();
    }

    /**
     * 检查题目内容
     *
     * @param list
     */
    private void checkQuestionContent(List<QuestionContentDTO> list) {
        if (list.size() > AppConstant.APP_MAX_QUESTION_NUM) {
            throw new BaseException(ExceptionConstant.QUESTION_NUM_OUT_MAX);
        }
        for (QuestionContentDTO questionContentDTO : list) {
            String title = questionContentDTO.getTitle();
            if (StrUtil.isBlank(title) || title.length() > QuestionConstant.QUESTION_TITLE_LENGTH) {
                throw new BaseException(ExceptionConstant.QUESTION_TITLE_ERROR);
            }
            if (questionContentDTO.getOptions().size() > QuestionConstant.OPTION_MAX_NUM) {
                throw new BaseException(ExceptionConstant.OPTION_NUM_OUT_MAX);
            }
            for (QuestionContentDTO.Option option : questionContentDTO.getOptions()) {
                if (BeanUtil.isEmpty(option)) {
                    throw new BaseException(ExceptionConstant.QUESTION_OPTION_ERROR);
                }
                if ((option.getKey() != null && option.getKey().length() > QuestionConstant.KEY_MAX_LENGTH) ||
                        (option.getValue() != null && option.getValue().length() > QuestionConstant.VALUE_MAX_LENGTH) ||
                        (option.getResult() != null && option.getResult().length() > QuestionConstant.RESULT_MAX_LENGTH) ||
                        (option.getScore() > QuestionConstant.SCORE_MAX_SIZE)
                ) {
                    throw new BaseException(ExceptionConstant.QUESTION_OPTION_ERROR);
                }
            }
        }
    }

    /**
     * 校验AI题目生成dto参数
     *
     * @param questionGenerateDTO
     */
    private void checkQuestionGenerateDTO(QuestionGenerateDTO questionGenerateDTO) {
        if (questionGenerateDTO.getQuestionDesc().length() > QuestionConstant.GEN_DESC_MAX_LENGTH) {
            throw new WarnException(ErrorCode.PARAMS_ERROR);
        }
        if (questionGenerateDTO.getQuestionNum() > QuestionConstant.GEN_QUESTION_MAX_NUM) {
            throw new WarnException(ErrorCode.PARAMS_ERROR);
        }
        if (questionGenerateDTO.getOptionNum() > QuestionConstant.GEN_OPTION_MAX_NUM) {
            throw new WarnException(ErrorCode.PARAMS_ERROR);
        }
    }

    /**
     * 生成题目的用户消息
     *
     * @param app
     * @param questionNumber
     * @param optionNumber
     * @return
     */
    private String getGenerateQuestionUserMessage(App app, int questionNumber, int optionNumber, String questionDesc) {
        StringBuilder userMessage = new StringBuilder();
        userMessage.append(app.getAppName()).append("\n");
        userMessage.append(questionDesc).append("\n");
        userMessage.append(AppTypeEnum.getEnumByValue(app.getAppType()).getText() + "类").append("\n");
        userMessage.append(questionNumber).append("\n");
        userMessage.append(optionNumber);
        return userMessage.toString();
    }

    /**
     * 导出题目信息表格
     *
     * @param response
     * @param ids
     */
    @Override
    public void exportQuestionToExcel(HttpServletResponse response, List<Long> ids) {

    }
}




